package edu.nju.mutest.mutator;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.EnclosedExpr;
import com.github.javaparser.ast.expr.Expression;
import edu.nju.mutest.util.CompilationUnitUtil;

import java.util.List;

abstract public class AbstractMutator implements Mutator {

    protected CompilationUnit origCU;
    protected final List<CompilationUnit> mutants = new NodeList<>();
    protected List<Expression> mutPoints = null;

    public AbstractMutator(CompilationUnit cu) {
        this.origCU = cu;
    }

    @SuppressWarnings("unused")
    public void setOrigCU(CompilationUnit origCU) {
        this.origCU = origCU;
        this.mutPoints = null;
    }

    @SuppressWarnings("unused")
    public List<CompilationUnit> getMutants() {
        if (mutants.isEmpty())
            System.out.println("Oops, seems no mutation has been conducted yet. Call mutate() first!");
        return mutants;
    }

    /**
     * replace old {@link Expression} by a new one , then add a copy of mutated {@link CompilationUnit} to mutants.
     *
     * @param oldExpr old Expression to be replaced
     * @param newExpr target Expression
     */
    protected void replaceNode(Expression oldExpr, Expression newExpr) {
        // Wrap new Expression in brackets to avoid interaction with external operators.
        newExpr = new EnclosedExpr(newExpr);
        oldExpr.replace(newExpr);
        CompilationUnit mutCU = CompilationUnitUtil.clone(origCU);
        newExpr.replace(oldExpr);
        mutants.add(mutCU);
    }

    /**
     * replace the {@link BinaryExpr.Operator} of {@link BinaryExpr}, then add a copy of mutated {@link CompilationUnit}
     * to mutants.
     *
     * @param be BinaryExpr to be changed
     * @param targetOp new Operator
     */
    protected void replaceBinaryOperator(BinaryExpr be, BinaryExpr.Operator targetOp) {
        BinaryExpr.Operator oldOp = be.getOperator();
        be.setOperator(targetOp);
        CompilationUnit mutCU = CompilationUnitUtil.clone(origCU);
        be.setOperator(oldOp);
        mutants.add(mutCU);
    }

    /**
     * Try to replace a BinaryExpr by its child or a similar BinaryExpr.
     *
     * @param be BinaryExpr as mutation point
     * @param ops if the {@link BinaryExpr.Operator} of 'be' is in 'ops', try to replace 'be' with 'be.left', 'be.right'
     *            or other BinaryExpr with {@link BinaryExpr.Operator} in ops,
     */
    protected void generateMutantsForBinaryExpr(BinaryExpr be, List<BinaryExpr.Operator> ops) {
        if (ops.contains(be.getOperator())) {
            for (BinaryExpr.Operator targetOp : ops) {
                if (be.getOperator() != targetOp) {
                    replaceBinaryOperator(be, targetOp);
                }
            }
            replaceNode(be, be.getLeft());
            replaceNode(be, be.getRight());
        }
    }
}
